/* $FreeBSD: releng/11.2/sys/dev/usb/quirk/usb_quirk.c 331722 2018-03-29 02:50:57Z eadler $ */
/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
 * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*lint -esym(459,usb_quirk_str)*/

#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#endif

MODULE_DEPEND(usb_quirk, usb, 1, 1, 1); /*lint !e19*/

#define    USB_DEV_QUIRKS_MAX 256
#define    USB_SUB_QUIRKS_MAX 8

struct usb_quirk_entry {
    uint16_t vid;
    uint16_t pid;
    uint16_t lo_rev;
    uint16_t hi_rev;
    uint16_t quirks[USB_SUB_QUIRKS_MAX];
};

static struct mtx usb_quirk_mtx;

#define    USB_QUIRK_VP(v,p,l,h,...) \
  { .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), \
    .quirks = { __VA_ARGS__ } }
#define    USB_QUIRK(v,p,l,h,...) \
  USB_QUIRK_VP(v, p, l, h, __VA_ARGS__)

static struct usb_quirk_entry usb_quirks[USB_DEV_QUIRKS_MAX] = {
    USB_QUIRK(4817, 5569, 0x0000, 0xffff, UQ_CFG_INDEX_1),
        USB_QUIRK(4817, 5374, 0x0000, 0xffff, UQ_MSC_EJECT_HUAWEISCSI3),
};
#undef USB_QUIRK_VP
#undef USB_QUIRK

static const char *usb_quirk_str[USB_QUIRK_MAX] = {/*lint -e26*/
    [UQ_NONE]        = "UQ_NONE",
    [UQ_MATCH_VENDOR_ONLY]    = "UQ_MATCH_VENDOR_ONLY",
    [UQ_AUDIO_SWAP_LR]    = "UQ_AUDIO_SWAP_LR",
    [UQ_AU_INP_ASYNC]    = "UQ_AU_INP_ASYNC",
    [UQ_AU_NO_FRAC]        = "UQ_AU_NO_FRAC",
    [UQ_AU_NO_XU]        = "UQ_AU_NO_XU",
    [UQ_BAD_ADC]        = "UQ_BAD_ADC",
    [UQ_BAD_AUDIO]        = "UQ_BAD_AUDIO",
    [UQ_BROKEN_BIDIR]    = "UQ_BROKEN_BIDIR",
    [UQ_BUS_POWERED]    = "UQ_BUS_POWERED",
    [UQ_HID_IGNORE]        = "UQ_HID_IGNORE",
    [UQ_KBD_IGNORE]        = "UQ_KBD_IGNORE",
    [UQ_KBD_BOOTPROTO]    = "UQ_KBD_BOOTPROTO",
    [UQ_UMS_IGNORE]        = "UQ_UMS_IGNORE",
    [UQ_MS_BAD_CLASS]    = "UQ_MS_BAD_CLASS",
    [UQ_MS_LEADING_BYTE]    = "UQ_MS_LEADING_BYTE",
    [UQ_MS_REVZ]        = "UQ_MS_REVZ",
    [UQ_NO_STRINGS]        = "UQ_NO_STRINGS",
    [UQ_POWER_CLAIM]    = "UQ_POWER_CLAIM",
    [UQ_SPUR_BUT_UP]    = "UQ_SPUR_BUT_UP",
    [UQ_SWAP_UNICODE]    = "UQ_SWAP_UNICODE",
    [UQ_CFG_INDEX_1]    = "UQ_CFG_INDEX_1",
    [UQ_CFG_INDEX_2]    = "UQ_CFG_INDEX_2",
    [UQ_CFG_INDEX_3]    = "UQ_CFG_INDEX_3",
    [UQ_CFG_INDEX_4]    = "UQ_CFG_INDEX_4",
    [UQ_CFG_INDEX_0]    = "UQ_CFG_INDEX_0",
    [UQ_ASSUME_CM_OVER_DATA]    = "UQ_ASSUME_CM_OVER_DATA",
    [UQ_MSC_NO_TEST_UNIT_READY]    = "UQ_MSC_NO_TEST_UNIT_READY",
    [UQ_MSC_NO_RS_CLEAR_UA]        = "UQ_MSC_NO_RS_CLEAR_UA",
    [UQ_MSC_NO_START_STOP]        = "UQ_MSC_NO_START_STOP",
    [UQ_MSC_NO_GETMAXLUN]        = "UQ_MSC_NO_GETMAXLUN",
    [UQ_MSC_NO_INQUIRY]        = "UQ_MSC_NO_INQUIRY",
    [UQ_MSC_NO_INQUIRY_EVPD]    = "UQ_MSC_NO_INQUIRY_EVPD",
    [UQ_MSC_NO_PREVENT_ALLOW]    = "UQ_MSC_NO_PREVENT_ALLOW",
    [UQ_MSC_NO_SYNC_CACHE]        = "UQ_MSC_NO_SYNC_CACHE",
    [UQ_MSC_SHUTTLE_INIT]        = "UQ_MSC_SHUTTLE_INIT",
    [UQ_MSC_ALT_IFACE_1]        = "UQ_MSC_ALT_IFACE_1",
    [UQ_MSC_FLOPPY_SPEED]        = "UQ_MSC_FLOPPY_SPEED",
    [UQ_MSC_IGNORE_RESIDUE]        = "UQ_MSC_IGNORE_RESIDUE",
    [UQ_MSC_WRONG_CSWSIG]        = "UQ_MSC_WRONG_CSWSIG",
    [UQ_MSC_RBC_PAD_TO_12]        = "UQ_MSC_RBC_PAD_TO_12",
    [UQ_MSC_READ_CAP_OFFBY1]    = "UQ_MSC_READ_CAP_OFFBY1",
    [UQ_MSC_FORCE_SHORT_INQ]    = "UQ_MSC_FORCE_SHORT_INQ",
    [UQ_MSC_FORCE_WIRE_BBB]        = "UQ_MSC_FORCE_WIRE_BBB",
    [UQ_MSC_FORCE_WIRE_CBI]        = "UQ_MSC_FORCE_WIRE_CBI",
    [UQ_MSC_FORCE_WIRE_CBI_I]    = "UQ_MSC_FORCE_WIRE_CBI_I",
    [UQ_MSC_FORCE_PROTO_SCSI]    = "UQ_MSC_FORCE_PROTO_SCSI",
    [UQ_MSC_FORCE_PROTO_ATAPI]    = "UQ_MSC_FORCE_PROTO_ATAPI",
    [UQ_MSC_FORCE_PROTO_UFI]    = "UQ_MSC_FORCE_PROTO_UFI",
    [UQ_MSC_FORCE_PROTO_RBC]    = "UQ_MSC_FORCE_PROTO_RBC",
    [UQ_MSC_EJECT_HUAWEI]        = "UQ_MSC_EJECT_HUAWEI",
    [UQ_MSC_EJECT_SIERRA]        = "UQ_MSC_EJECT_SIERRA",
    [UQ_MSC_EJECT_SCSIEJECT]    = "UQ_MSC_EJECT_SCSIEJECT",
    [UQ_MSC_EJECT_REZERO]        = "UQ_MSC_EJECT_REZERO",
    [UQ_MSC_EJECT_ZTESTOR]        = "UQ_MSC_EJECT_ZTESTOR",
    [UQ_MSC_EJECT_CMOTECH]        = "UQ_MSC_EJECT_CMOTECH",
    [UQ_MSC_EJECT_WAIT]        = "UQ_MSC_EJECT_WAIT",
    [UQ_MSC_EJECT_SAEL_M460]    = "UQ_MSC_EJECT_SAEL_M460",
    [UQ_MSC_EJECT_HUAWEISCSI]    = "UQ_MSC_EJECT_HUAWEISCSI",
    [UQ_MSC_EJECT_HUAWEISCSI2]    = "UQ_MSC_EJECT_HUAWEISCSI2",
    [UQ_MSC_EJECT_HUAWEISCSI3]    = "UQ_MSC_EJECT_HUAWEISCSI3",
    [UQ_MSC_EJECT_TCT]        = "UQ_MSC_EJECT_TCT",
    [UQ_BAD_MIDI]            = "UQ_BAD_MIDI",
    [UQ_AU_VENDOR_CLASS]        = "UQ_AU_VENDOR_CLASS",
    [UQ_SINGLE_CMD_MIDI]        = "UQ_SINGLE_CMD_MIDI",
};/*lint +e26*/

/*------------------------------------------------------------------------*
 *    usb_quirkstr
 *
 * This function converts an USB quirk code into a string.
 *------------------------------------------------------------------------*/
static const char *
usb_quirkstr(uint16_t quirk)
{
    return ((quirk < USB_QUIRK_MAX) ?
        usb_quirk_str[quirk] : "USB_QUIRK_UNKNOWN");
}

/*------------------------------------------------------------------------*
 *    usb_test_quirk_by_info
 *
 * Returns:
 * 0: Quirk not found
 * Else: Quirk found
 *------------------------------------------------------------------------*/
static uint8_t
usb_test_quirk_by_info(const struct usbd_lookup_info *info, uint16_t quirk)
{
    uint16_t x;
    uint16_t y;

    if (quirk == UQ_NONE)
        goto done;

    mtx_lock(&usb_quirk_mtx);

    for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
        /* see if quirk information does not match */
        if ((usb_quirks[x].vid != info->idVendor) ||
            (usb_quirks[x].lo_rev > info->bcdDevice) ||
            (usb_quirks[x].hi_rev < info->bcdDevice)) {
            continue;
        }
        /* see if quirk only should match vendor ID */
        if (usb_quirks[x].pid != info->idProduct) {
            if (usb_quirks[x].pid != 0)
                continue;

            for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) {
                if (usb_quirks[x].quirks[y] == UQ_MATCH_VENDOR_ONLY)
                    break;
            }
            if (y == USB_SUB_QUIRKS_MAX)
                continue;
        }
        /* lookup quirk */
        for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) {
            if (usb_quirks[x].quirks[y] == quirk) {
                mtx_unlock(&usb_quirk_mtx);
                DPRINTF("Found quirk '%s'.\n", usb_quirkstr(quirk));
                return (1);
            }
        }
        /* no quirk found */
        break;
    }
    mtx_unlock(&usb_quirk_mtx);
done:
    return (0);            /* no quirk match */
}

static struct usb_quirk_entry *
usb_quirk_get_entry(uint16_t vid, uint16_t pid,
    uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc)
{
    uint16_t x;

    mtx_assert(&usb_quirk_mtx, MA_OWNED);/*lint !e506*/

    if ((vid | pid | lo_rev | hi_rev) == 0) {
        /* all zero - special case */
        return (usb_quirks + USB_DEV_QUIRKS_MAX - 1);
    }
    /* search for an existing entry */
    for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
        /* see if quirk information does not match */
        if ((usb_quirks[x].vid != vid) ||
            (usb_quirks[x].pid != pid) ||
            (usb_quirks[x].lo_rev != lo_rev) ||
            (usb_quirks[x].hi_rev != hi_rev)) {
            continue;
        }
        return (usb_quirks + x);
    }

    if (do_alloc == 0) {
        /* no match */
        return (NULL);
    }
    /* search for a free entry */
    for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
        /* see if quirk information does not match */
        if ((usb_quirks[x].vid |
            usb_quirks[x].pid |
            usb_quirks[x].lo_rev |
            usb_quirks[x].hi_rev) != 0) {
            continue;
        }
        usb_quirks[x].vid = vid;
        usb_quirks[x].pid = pid;
        usb_quirks[x].lo_rev = lo_rev;
        usb_quirks[x].hi_rev = hi_rev;

        return (usb_quirks + x);
    }

    /* no entry found */
    return (NULL);
}

/*------------------------------------------------------------------------*
 *    usb_quirk_ioctl - handle quirk IOCTLs
 *
 * Returns:
 * 0: Success
 * Else: Failure
 *------------------------------------------------------------------------*/
static int
usb_quirk_ioctl(unsigned long cmd, caddr_t data,
    int fflag, struct thread *td)
{
    struct usb_gen_quirk *pgq;
    struct usb_quirk_entry *pqe;
    uint32_t x;
    uint32_t y;
    int err;

    (void)td;

    switch (cmd) {
    case USB_DEV_QUIRK_GET:
        pgq = (void *)data;
        x = pgq->index % USB_SUB_QUIRKS_MAX;
        y = pgq->index / USB_SUB_QUIRKS_MAX;
        if (y >= USB_DEV_QUIRKS_MAX) {
            return (EINVAL);
        }
        mtx_lock(&usb_quirk_mtx);
        /* copy out data */
        pgq->vid = usb_quirks[y].vid;
        pgq->pid = usb_quirks[y].pid;
        pgq->bcdDeviceLow = usb_quirks[y].lo_rev;
        pgq->bcdDeviceHigh = usb_quirks[y].hi_rev;
        (void)strlcpy(pgq->quirkname,
            usb_quirkstr(usb_quirks[y].quirks[x]),
            sizeof(pgq->quirkname));
        mtx_unlock(&usb_quirk_mtx);
        return (0);        /* success */

    case USB_QUIRK_NAME_GET:
        pgq = (void *)data;
        x = pgq->index;
        if (x >= USB_QUIRK_MAX) {
            return (EINVAL);
        }
        (void)strlcpy(pgq->quirkname,
            usb_quirkstr(x), sizeof(pgq->quirkname));
        return (0);        /* success */

    case USB_DEV_QUIRK_ADD:
        pgq = (void *)data;

        /* check privileges */
        err = priv_check(curthread, PRIV_DRIVER);
        if (err) {
            return (err);
        }
        /* convert quirk string into numerical */
        for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) {
            if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) {
                break;
            }
        }
        if (y == USB_DEV_QUIRKS_MAX) {
            return (EINVAL);
        }
        if (y == UQ_NONE) {
            return (EINVAL);
        }
        mtx_lock(&usb_quirk_mtx);
        pqe = usb_quirk_get_entry(pgq->vid, pgq->pid,
            pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 1);
        if (pqe == NULL) {
            mtx_unlock(&usb_quirk_mtx);
            return (EINVAL);
        }
        for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
            if (pqe->quirks[x] == UQ_NONE) {
                pqe->quirks[x] = y;
                break;
            }
        }
        mtx_unlock(&usb_quirk_mtx);
        if (x == USB_SUB_QUIRKS_MAX) {
            return (ENOMEM);
        }
        return (0);        /* success */

    case USB_DEV_QUIRK_REMOVE:
        pgq = (void *)data;
        /* check privileges */
        err = priv_check(curthread, PRIV_DRIVER);
        if (err) {
            return (err);
        }
        /* convert quirk string into numerical */
        for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) {
            if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) {
                break;
            }
        }
        if (y == USB_DEV_QUIRKS_MAX) {
            return (EINVAL);
        }
        if (y == UQ_NONE) {
            return (EINVAL);
        }
        mtx_lock(&usb_quirk_mtx);
        pqe = usb_quirk_get_entry(pgq->vid, pgq->pid,
            pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 0);
        if (pqe == NULL) {
            mtx_unlock(&usb_quirk_mtx);
            return (EINVAL);
        }
        for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
            if (pqe->quirks[x] == y) {
                pqe->quirks[x] = UQ_NONE;
                break;
            }
        }
        if (x == USB_SUB_QUIRKS_MAX) {
            mtx_unlock(&usb_quirk_mtx);
            return (ENOMEM);
        }
        for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
            if (pqe->quirks[x] != UQ_NONE) {
                break;
            }
        }
        if (x == USB_SUB_QUIRKS_MAX) {
            /* all quirk entries are unused - release */
            (void)memset_s(pqe, sizeof(*pqe), 0, sizeof(*pqe));
        }
        mtx_unlock(&usb_quirk_mtx);
        return (0);        /* success */

    default:
        break;
    }
    return (ENOIOCTL);
}

void
usb_quirk_init(void *arg)
{
    /* initialize mutex */
    mtx_init(&usb_quirk_mtx, "USB quirk", NULL, MTX_DEF);

    /* register our function */
    usb_test_quirk_p = &usb_test_quirk_by_info; /*lint !e546*/
    usb_quirk_ioctl_p = &usb_quirk_ioctl; /*lint !e546*/
}

